/*
 *
 * FileName: SCMClientImpl.java
 * Author:   wx:fdzhangc
 * Date:     2021/11/24 10:31
 * Description: //模块目的、功能描述
 * History: //修改记录
 * <author>      <time>      <version>    <desc>
 * 修改人姓名    修改时间    版本号       描述
 */
package com.zhangc.zcscm.client;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zhangc.zcscm.util.CompressUtils;
import com.zhangc.zcscm.util.EncryptUtil;
import com.zhangc.zcscm.util.HttpClient;
import com.zhangc.zcscm.util.ParamUtil;
import com.zhangc.zcscm.zookeeper.Deserializer;
import com.zhangc.zcscm.zookeeper.ZkClient;
import com.zhangc.zcscm.zookeeper.ZkConnection;
import com.zhangc.zcscm.zookeeper.ZkNode;

/**
 * 功能描述:<br>
 *
 * @author wx:fdzhangc
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
public class SCMClientImpl implements SCMClient {
    private static Logger logger = LoggerFactory.getLogger(SCMClientImpl.class);
    private static final SCMClientImpl.CompressStrDeserializer DESERIALIZER = new SCMClientImpl.CompressStrDeserializer();
    private final ConcurrentHashMap<String, SCMNode> scmNodes = new ConcurrentHashMap();
    private static final String PATH_SEPARATOR = "/";
    private static final String GLOBAL_PATH = "/global";
    private static final String ROOT = "/scmv1";
    private static final String DB_ROOT = "/scm";
    private static final String GLOBAL_START = "global/";
    private ZkClient zkClient;
    private SCMConfiguration config;

    /**
     * @deprecated
     */
    @Deprecated
    public static SCMClient getInstance() throws SCMException {
        return SCMClientFactory.getSCMClient();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public static void setConfigFilePath(String path) {
        SCMClientFactory.setConfigFilePath(path);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public static SCMClient getInstance(String configFilePath) throws SCMException {
        return SCMClientFactory.getSCMClient(configFilePath);
    }

    protected SCMClientImpl(SCMConfiguration config) throws SCMException {
        //VersionStatistics.reportVersion(config.getAppCode(), config.getScmServer(), SCMClientImpl.class);
        this.config = config;
        this.zkClient = new ZkClient(config.getZkServer().trim(),
                ZkConnection.digestCredentials(config.getAppCode().trim(), config.getSecretKey().trim()));
    }

    public SCMNode getConfig(String path) {
        String formatedPath = parse(path);
        SCMNode scmNode = (SCMNode) this.scmNodes.get(formatedPath);
        if (scmNode != null) {
            return scmNode;
        } else {
            synchronized (this.scmNodes) {
                scmNode = (SCMNode) this.scmNodes.get(formatedPath);
                if (scmNode != null) {
                    return scmNode;
                } else {
                    ZkNode<String> zkNode = new ZkNode(this.zkClient, getFullPath(this.config.getAppCode(), formatedPath), DESERIALIZER,
                            true, false, true);
                    scmNode = new SCMNodeImpl(this.config, formatedPath, zkNode);
                    this.scmNodes.put(formatedPath, scmNode);
                    return scmNode;
                }
            }
        }
    }

    private static String getPropertyValue(String key) {
        String value = System.getenv(key);
        if (StringUtils.isEmpty(value)) {
            value = System.getProperty(key);
        }

        return value;
    }

    public static String parse(String path) {
        if (path != null && path.length() != 0) {
            int start = -1;
            int end = -1;
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < path.length(); ++i) {
                if (path.charAt(i) == '$' && start == -1 && i < path.length() - 1 && path.charAt(i + 1) == '{') {
                    start = i++;
                } else if (path.charAt(i) == '}' && start != -1) {
                    end = i;
                }

                if (start == -1) {
                    sb.append(path.charAt(i));
                }

                if (start != -1 && end != -1) {
                    String value = getPropertyValue(path.substring(start + 2, end));
                    if (StringUtils.isEmpty(value)) {
                        throw new IllegalArgumentException(
                                String.format("No system property %s of %s", path.substring(start + 2, end), path));
                    }

                    sb.append(value);
                    start = -1;
                    end = -1;
                }
            }

            if (start != -1 && end == -1) {
                sb.append(path.substring(start));
            }

            return sb.toString();
        } else {
            return path;
        }
    }

    public static String getFullPath(String appCode, String path) {
        if (path.indexOf("/") == 0) {
            path = StringUtils.substringAfter(path, "/");
        }

        String fullPath;
        if (path.startsWith("global/")) {
            fullPath = "/scmv1/" + path;
        } else {
            fullPath = "/scmv1/" + appCode + "/" + path;
        }

        return fullPath;
    }

    public static String getFullOperationPath(String appCode, String path) {
        if (path.indexOf("/") == 0) {
            path = StringUtils.substringAfter(path, "/");
        }

        return "/scm/" + appCode + "/" + path;
    }

    public void destroy() {
        synchronized (this.scmNodes) {
            Iterator i$ = this.scmNodes.values().iterator();

            while (i$.hasNext()) {
                SCMNode scmNode = (SCMNode) i$.next();

                try {
                    scmNode.destroy();
                } catch (Throwable var6) {
                }
            }

            this.zkClient.close();
        }
    }

    public void destroySCMNode(String path) {
        String formatedPath = parse(path);
        synchronized (this.scmNodes) {
            try {
                SCMNode scmNode = (SCMNode) this.scmNodes.get(formatedPath);
                if (scmNode != null) {
                    scmNode.destroy();
                    this.scmNodes.remove(formatedPath);
                }
            } catch (Throwable var6) {
            }

        }
    }

    public void destroySCMNodeByAPPCode(String appCode, String path) {
        String formatedPath = parse(path);
        if (formatedPath.indexOf("/") == 0) {
            formatedPath = StringUtils.substringAfter(formatedPath, "/");
        }

        if (appCode.indexOf("/") == 0) {
            appCode = StringUtils.substringAfter(appCode, "/");
        }

        String zkPath = "/scmv1/" + appCode + "/" + formatedPath;
        synchronized (this.scmNodes) {
            try {
                SCMNode scmNode = (SCMNode) this.scmNodes.get(zkPath);
                if (scmNode != null) {
                    scmNode.destroy();
                    this.scmNodes.remove(zkPath);
                }
            } catch (Throwable var8) {
            }

        }
    }

    public int createConfig(String user, String path, String value) throws SCMException {
        return this.remoteOperationConfig(user, getFullOperationPath(this.config.getAppCode(), path), value, "create");
    }

    public int updateConfig(String user, String path, String value) throws SCMException {
        return this.remoteOperationConfig(user, getFullOperationPath(this.config.getAppCode(), path), value, "update");
    }

    public int updateGlobalConfig(String path, String value) throws SCMException {
        return this.remoteOperationConfig(this.config.getAppCode(), getFullOperationPath("/global".substring(1), path), value, "update");
    }

    public int deleteConfig(String user, String path) throws SCMException {
        return this.remoteOperationConfig(user, getFullOperationPath(this.config.getAppCode(), path), "", "delete");
    }

    public int createConfigByApp(String appCode, String path, String value) throws SCMException {
        return this.remoteOperationConfig(appCode, getFullOperationPath(appCode, path), value, "create");
    }

    public int updateConfigByApp(String appCode, String path, String value) throws SCMException {
        return this.remoteOperationConfig(appCode, getFullOperationPath(appCode, path), value, "update");
    }

    public int deleteConfigByApp(String appCode, String path) throws SCMException {
        return this.remoteOperationConfig(appCode, getFullOperationPath(appCode, path), "", "delete");
    }

    private int remoteOperationConfig(String user, String path, String config, String operationType) throws SCMException {
        Map<String, Object> map = new HashMap();
        map.put("appCode", this.config.getAppCode());
        map.put("path", path);
        map.put("config", config);
        map.put("operationType", operationType);
        map.put("user", user);
        String result = this.sendDataByPost(this.config.getScmServer() + "/operationConfig.htm", map);
        String[] resultsStrings = result.split(",");
        if ("failure".equals(resultsStrings[0])) {
            throw new SCMException(resultsStrings[1]);
        } else {
            return 1;
        }
    }

    public String updateSwitchConfig(String systemCode, String appCode, String path, String value) {
        Map<String, Object> map = new HashMap();
        map.put("appCode", this.config.getAppCode());
        map.put("switchAppCode", appCode);
        map.put("path", getFullOperationPath(appCode, path));
        map.put("config", value);
        map.put("systemCode", systemCode);
        return this.sendDataByPost(this.config.getScmServer() + "/updateSwitchConfig.htm", map);
    }

    public String checkSwitchConfig(String systemCode, String appCode, String path) {
        Map<String, Object> map = new HashMap();
        map.put("appCode", this.config.getAppCode());
        map.put("switchAppCode", appCode);
        map.put("path", path);
        map.put("systemCode", systemCode);
        return this.sendDataByPost(this.config.getScmServer() + "/checkSwitchConfig.htm", map);
    }

    protected String sendDataByPost(String url, Map<String, Object> dataMap) {
        if (url != null && url.length() != 0) {
            dataMap.put("timeStamp", System.currentTimeMillis());

            try {
                String digestStr = ParamUtil.getParamFromMap(dataMap);
                String mac = EncryptUtil.encryptHMAC(digestStr, this.config.getSecretKey());
                String paramStr = ParamUtil.getEncodedParamFromMap(dataMap);
                String result = HttpClient.sendByPost(url, paramStr + "&mac=" + mac, 30000, 3, 2000);
                logger.info("Result from SCM server:" + result);
                return result;
            } catch (Exception var7) {
                throw new SCMException("Exception occur when send data to scm server.", var7);
            }
        } else {
            throw new IllegalArgumentException("url is empty!");
        }
    }

    public ZkClient getInternalZkClient() {
        return this.zkClient;
    }

    public String getAppCode() {
        return this.config.getAppCode();
    }

    public String getSecureKey(String appCode) {
        try {
            ZkClient.ReadResult readResult = this.zkClient.readData("/scmv1/" + appCode, 30000L);
            return new String(readResult.data);
        } catch (Exception var3) {
            throw new SCMException(var3.getMessage(), var3);
        }
    }

    public String getSecretKey() {
        return this.config.getSecretKey();
    }

    public String getZkServer() {
        return this.config.getZkServer();
    }

    public SCMNode getGlobalConfig(String path) throws SCMException {
        if (path.indexOf("/") == 0) {
            path = StringUtils.substringAfter(path, "/");
        }

        return this.getConfig("/global/" + path);
    }

    public Environment getEnvironment() {
        return this.config.getEnvironment();
    }

    public SCMNode readConfig(String appCode, String path) {
        if (!StringUtils.isEmpty(appCode) && !StringUtils.isEmpty(path)) {
            String formatedPath = parse(path);
            if (formatedPath.indexOf("/") == 0) {
                formatedPath = StringUtils.substringAfter(formatedPath, "/");
            }

            if (appCode.indexOf("/") == 0) {
                appCode = StringUtils.substringAfter(appCode, "/");
            }

            String zkPath = "/scmv1/" + appCode + "/" + formatedPath;
            SCMNode scmNode = (SCMNode) this.scmNodes.get(zkPath);
            if (scmNode != null) {
                return scmNode;
            } else {
                synchronized (this.scmNodes) {
                    scmNode = (SCMNode) this.scmNodes.get(zkPath);
                    if (scmNode != null) {
                        return scmNode;
                    } else {
                        ZkNode<String> zkNode = new ZkNode(this.zkClient, zkPath, DESERIALIZER, true, false, true);
                        scmNode = new SCMNodeImpl(this.config, zkPath, zkNode);
                        this.scmNodes.put(zkPath, scmNode);
                        return scmNode;
                    }
                }
            }
        } else {
            return null;
        }
    }

    public static class CompressStrDeserializer implements Deserializer<String> {
        public CompressStrDeserializer() {
        }

        public String deserialize(byte[] data) {
            return CompressUtils.deserialize(data);
        }
    }
}
